/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License");  you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * http//www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is ONT_INDEXER.
 *
 * The Initial Developer of the Original Code is University of Roma Tor Vergata.
 * Portions created by University of Roma Tor Vergata are Copyright (C) 2006.
 * All Rights Reserved.
 *
 * ONT_INDEXER was developed by the Artificial Intelligence Research Group
 * (ai-nlp.info.uniroma2.it) at the University of Roma Tor Vergata
 * Current information about ONT_INDEXER can be obtained at 
 * http//ai-nlp.info.uniroma2.it/software/...
 *
 */

/*
 * Contributor(s): Armando Stellato stellato@info.uniroma2.it
 */
package it.uniroma2.art.owlart.langsearch.search;

import it.uniroma2.art.owlart.langsearch.OWLSearchEngine;
import it.uniroma2.art.owlart.langsearch.index.RDFIndexManager;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.models.OWLModel;
import it.uniroma2.art.owlart.vocabulary.RDFResourceRolesEnum;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.TermFreqVector;
import org.apache.lucene.index.TermPositionVector;
import org.apache.lucene.index.TermVectorOffsetInfo;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Collections2;

public class OntIndexSearcher {

	public static final int EXACT_MATCH = 1;

	/**
	 * @deprecated use {@link #EXACT_MATCH} instead
	 */
	public static final int EXACT_STRATEGY = 1;
	public static final int FUZZY_MATCH = 2;
	/**
	 * @deprecated use {@link #FUZZY_MATCH} instead
	 */
	public static final int SIMILARITY_STRATEGY = 2;
	private String THRESHOLD = "0.9";

	String IndexDir;

	protected static Logger logger = LoggerFactory.getLogger(OWLSearchEngine.class);

	private Collection<String> URILangs;
	private RDFIndexManager idxMgr;

	public enum TargetOfSearch {
		LABELS, RESOURCES
	}

	// ATTENZIONE! EVERYTHING IS STILL WORK-IN-PROGRESS here.
	// The OntIndexer searcher now takes an IndexManager as input, but it uses its writers to get info
	// though the writers are (now) only there if the OntIndexer has built the index
	/**
	 * @param idxMgr
	 * @param URILangs
	 */
	public OntIndexSearcher(RDFIndexManager idxMgr, Collection<String> URILangs) {
		this.idxMgr = idxMgr;
		this.URILangs = URILangs;
	}

	public Collection<ARTURIResource> findResources(OWLModel m, String indexID, String name, String lang,
			int strategy) throws IndexAccessException, ParseException {
		Collection<String> standardCollection;

		standardCollection = searchTarget(name, lang, indexID, strategy, TargetOfSearch.RESOURCES);
		logger.debug("results: " + standardCollection);
		ArrayList<ARTURIResource> result = new ArrayList<ARTURIResource>();
		Iterator<String> it = standardCollection.iterator();
		while (it.hasNext()) {
			ARTURIResource ontResource = m.createURIResource(it.next());
			logger.debug(indexID + " found: " + ontResource);
			result.add(ontResource);
		}
		logger.debug("\n");
		return result;

	}

	public Collection<String> findLabels(OWLModel m, String indexID, String name, String lang, int strategy)
			throws IndexAccessException, ParseException {

		return searchTarget(name, lang, indexID, strategy, TargetOfSearch.LABELS);
	}

	public Collection<ARTURIResource> findClses(OWLModel m, String name, String lang, int strategy)
			throws IndexAccessException, ParseException {
		return findResources(m, RDFResourceRolesEnum.cls.toString(), name, lang, strategy);
	}

	public Collection<ARTURIResource> findProperties(OWLModel m, String name, String lang, int strategy)
			throws IndexAccessException, ParseException {
		return findResources(m, RDFResourceRolesEnum.property.toString(), name, lang, strategy);
	}

	public Collection<ARTURIResource> findIndividuals(OWLModel m, String name, String lang, int strategy)
			throws IndexAccessException, ParseException {
		return findResources(m, RDFResourceRolesEnum.individual.toString(), name, lang, strategy);
	}

	public Collection<RDFSearchResult> searchFull(String name, String lang, String indexID, int strategy)
			throws IndexAccessException, ParseException {

		ArrayList<RDFSearchResult> results = new ArrayList<RDFSearchResult>();

		IndexSearcher searcher=null;
		try {
			searcher = createSearcher(lang, indexID);
			ScoreDoc[] hits = search(searcher, name, lang, indexID, strategy);

			for (int j = 0; j < hits.length; j++)
				results.add(new RDFSearchResult(searcher.doc(hits[j].doc), hits[j].score));

		} catch (IOException e) {
			throw new IndexAccessException(e);
		} finally {
			try {
				if (searcher!=null) {
					searcher.getIndexReader().close();
					searcher.close();
				}
			} catch (IOException e) {
				throw new IndexAccessException(e);				
			}			
		}

		logger.debug("end of search");
		return results;
	}

	/**
	 * retrieves, depending on the <code>srcTrg</code> value, either the URIs of the retrieved resources, or
	 * the set of labels which have been considered for the query
	 * 
	 * @param name
	 * @param ontoType
	 * @param lang
	 * @param strategy
	 * @return
	 * @throws ParseException
	 * @throws IOException
	 */
	public Collection<String> searchTarget(String name, String lang, String indexID, int strategy,
			TargetOfSearch srcTgt) throws IndexAccessException, ParseException {

		ArrayList<String> results = new ArrayList<String>();
		
		IndexSearcher searcher = null;
		try {
			searcher = createSearcher(lang, indexID);
			ScoreDoc[] hits = search(searcher, name, lang, indexID, strategy);

			// IndexReader idxReader = IndexReader
			// .open(idxMgr.getLangIndexDirectoryForTypeLabels(indexID, lang));
			if (srcTgt == TargetOfSearch.LABELS)
				for (int j = 0; j < hits.length; j++) {
					// TODO depending on what I want exactly to retrieve, it would be better to get exactly
					// the labels that matched against the search. However, the termFreqVector for the docId
					// is null (maybe not stored?). In any case, I'm retrieving here the entire set of labels
					// for the resource, and not the matching ones

					// int docId = hits[j].doc;
					// TermFreqVector termFreqVector = idxReader.getTermFreqVector(docId, lang);
					// String[] terms = termFreqVector.getTerms();
					// for (String term : terms)
					// results.add(term);

					// CURRENT METHOD WHICH RETRIEVES ALL LABELS (NOT ONLT THE MATCHING ONES)
					String labels = searcher.doc(hits[j].doc).get(lang);
					String[] labelsArray = labels.split(RDFIndexManager._labelSplittingSeq);
					// if field is available, then it should always contain one label, and a split over
					// RDFIndexManager._labelSplittingSeq should have the first array element as the empty
					// string, that's the reason for starting from 1
					if (labelsArray.length > 1)
						for (int i = 1; i < labelsArray.length; i++)
							results.add(labelsArray[i]);
				}

			else
				// RESOURCES
				for (int j = 0; j < hits.length; j++) {
					results.add(searcher.doc(hits[j].doc).get("URI"));
					logger.info("found for element field: " + searcher.doc(hits[j].doc).get(lang));
				}

		} catch (IOException e) {
			throw new IndexAccessException(e);
		} finally {
			try {
				if (searcher!=null) {
					searcher.getIndexReader().close();
					searcher.close();
				}
			} catch (IOException e) {
				throw new IndexAccessException(e);				
			}			
		}

		logger.debug("end of search");
		return results;
	}

	protected ScoreDoc[] search(IndexSearcher searcher, String name, String lang, String indexID, int strategy)
			throws ParseException, IndexAccessException {

		if (strategy == FUZZY_MATCH)
			name = name + "~" + THRESHOLD;

		Analyzer analyzer = idxMgr.getIndexAnalyzer(lang);

		if (analyzer == null) {
			logger.debug("analyzer not available from indexManager, recreating it");
			// TODO prepareS... initializes actually the Analyzer. Better to remove this prepare method
			// (reminiscence of older bigger method) and be sure that the analyzer is only created at
			// initialization and then only got from the map
			analyzer = idxMgr.prepareSuitableAnalyzer(lang);
		}
		logger.debug("analyzer used for analyzing the query: " + analyzer);

		String queryString = name;

		// if (URILangs.contains(lang)) {
		// queryString += " OR " + "localName:" + name;
		// }

		Query query = null;

		logger.debug("performing query for: " + name);
		logger.debug("processed query string: " + queryString);

		QueryParser queryParser = new QueryParser(Version.LUCENE_36, lang, analyzer);
		queryParser.setAllowLeadingWildcard(true);
		// queryParser.setAutoGeneratePhraseQueries(true);
		query = queryParser.parse(queryString);

		// query = new WildcardQuery(new Term(lang, queryString));

		logger.debug("PARSED QUERY: " + query);

		ScoreDoc[] hits = null;

		// TODO something has to be changed here after moving to Lucene 3.6.2. Previously there were no
		// limit to results, today there is a new interface for results in a page, a new Collector
		// interface etc.. see: http://lucenetutorial.com/lucene-in-5-minutes.html
		try {
			hits = searcher.search(query, 10000).scoreDocs;
			searcher.close();
		} catch (IOException e) {
			throw new IndexAccessException(e);
		}

		logger.debug("NUMBER OF MATCHING CONTACTS: " + hits.length);
		return hits;
	}

	private IndexSearcher createSearcher(String lang, String indexID) throws IOException {

		IndexSearcher searcher = null;
		Directory indexDir = idxMgr.getLangIndexDirectoryForTypeLabels(indexID, lang);

		if (URILangs.contains("I would temporaneously remove this case. I do not need a MultiSearcher")) {
			logger.debug("entering multiindex search:");
			IndexSearcher labelSearcher = new IndexSearcher(IndexReader.open(indexDir));
			Directory localNamesIndexDir = null;

			IndexSearcher nameSearcher = new IndexSearcher(IndexReader.open(localNamesIndexDir));
			// IndexSearcher[] searchersArray = { labelSearcher, nameSearcher };
			// searcher = new MultiSearcher(labelSearcher, nameSearcher);
			searcher = null;
		} else {
			logger.debug("entering single search:");
			searcher = new IndexSearcher(IndexReader.open(indexDir));
		}

		return searcher;
	}

	public String getTHRESHOLD() {
		return THRESHOLD;
	}

	public void setTHRESHOLD(String threshold) {
		THRESHOLD = threshold;
	}

}
